/*

Copyright 2009 Nicola Dal Pont

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Version ${MAJOR_VERSION}.${MINOR_VERSION}${BETA_MARK} Build ${BUILD_NUMBER}

*/
package com.astoolkit.workflow
{
	import com.astoolkit.workflow.api.*;
	import com.astoolkit.workflow.constant.TaskStatus;
	
	import flash.events.Event;
	import flash.events.IEventDispatcher;
	import flash.utils.getQualifiedClassName;
	
	import mx.logging.ILogger;
	import mx.logging.Log;
	import mx.rpc.AsyncToken;
	import mx.rpc.IResponder;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.utils.UIDUtil;

	public class SuspendableListenersRegistry implements ISuspendableListenersRegistry
	{
		
		private static const LOGGER : ILogger = 
			Log.getLogger( getQualifiedClassName( SuspendableListenersRegistry ).replace(/:+/g, "." ) );
		
		/**
		 * @private
		 */
		private var _suspendableListeners : Object = {};
		
		/**
		 * @private
		 */
		private var _resumeCallBacks : Array;

		public function SuspendableListenersRegistry()
		{
			
		}
		
		/**
		 * registers a listener to an external event dispatcher.<br><br>
		 * Use this function instead of registering to an even dispatcher directly.
		 * That makes sure that events dispatched when the current
		 * workflow is in suspended mode, will trigger
		 * our task's listeners only when the workflow is resumed,
		 * therefore preventing flow inconsistencies.<br><br>
		 * It also makes sure that events dispatched as a result
		 * of async requests are discared if the task that triggered
		 * the request fails. For instance let's say that our
		 * task waits for a remote response to call complete(). 
		 * If a parallel task makes the current iteration of 
		 * the wrapping workflow fail, before the response comes back,
		 *  and our task sends the request again in the next iteration, 
		 * it's likely that it will receive two responses.
		 */
		public function add( 
			inTask : IWorkflowTask,
			inTarget : IEventDispatcher, 
			inType : String, 
			inHandler : Function, 
			inUseCapture : Boolean = false, 
			inPriority : uint = 0, 
			inUseWeakReference : Boolean = false ) : void
		{
			inTarget.addEventListener( inType, suspendableEventHandler, inUseCapture, inPriority, inUseWeakReference );
			if( !_suspendableListeners.hasOwnProperty( UIDUtil.getUID( inTarget ) + inType ) )
				_suspendableListeners[ UIDUtil.getUID( inTarget ) + inType ] = [];
			_suspendableListeners[ UIDUtil.getUID( inTarget ) + inType ].push(  new SuspendableListener( inTask, inHandler, inTask.currentThread, inTarget, inType ) );
		}
		
		/**
		 * @private
		 */
		public function remove(
			inTask : IWorkflowTask,
			inTarget : IEventDispatcher, 
			inType : String, 
			inHandler : Function ) : void
		{
			inTarget.removeEventListener( inType, suspendableEventHandler);
			var fns : Array = _suspendableListeners[ UIDUtil.getUID( inTarget ) + inType ] as Array;
			if( fns == null )
				return;
			for( var i : int = 0; i< fns.length; i++ )
			{
				if( inHandler == fns[i].handler )
				{
					fns.splice( i, 1 );
					return;
				}
			}
			
		}
		
		/**
		 * @private
		 */
		private function suspendableEventHandler( inEvent : Event ) : void
		{
			var fns : Array = ( _suspendableListeners[ UIDUtil.getUID( inEvent.target ) + inEvent.type ] as Array);
			if( fns == null )
				return;
			fns = fns.concat();
			var newFns : Array = [];
			for each( var fn : SuspendableListener in fns )
			{
				if( fn.thread == fn.task.currentThread )
				{
					newFns.push( fn );
					var t : uint = fn.task.currentThread;
					if( fn.task.status == TaskStatus.SUSPENDED )
					{
						addResumeCallBack( 
							function() : void
							{
								if( fn.thread == t )
									fn.handler( inEvent );
								else
									remove( fn.task, fn.target, fn.type, fn.handler ); 
							} );
					}
					else if( fn.task.status != TaskStatus.RUNNING )
					{
						LOGGER.error( "An event listener registered by task " + fn.task + 
							" is active while the task is not running. Ignoring dispatched event \"" +
							inEvent.type + "\"" );
					}
					else
					{
						fn.handler( inEvent );
					}
				}
			}
		}
		
		public function createResponder( 
			inTask : IWorkflowTask, 
			inResultFunction : Function, 
			inFaultFunction : Function ) : IResponder
		{
			var responder : SuspendableResponder = 
				new SuspendableResponder( inResultFunction, inFaultFunction );
			add( inTask, responder, ResultEvent.RESULT, 
				function( inEvent : ResultEvent ) : void
				{
					inResultFunction( inEvent.result );
				} );
			add( inTask, responder, FaultEvent.FAULT, 
				function( inEvent : FaultEvent ) : void
				{
					inFaultFunction( inEvent.fault );
				} );
			return responder;
		}
		
		/**
		 * @private
		 */
		public function addResumeCallBack( inFunction : Function ) : void
		{
			if( !_resumeCallBacks )
				initResumeCallBacks();
			_resumeCallBacks.push( inFunction );
		}
		
		/**
		 * @private
		 */
		public function initResumeCallBacks() : void
		{
			_resumeCallBacks = [];
		}
		
		
		/**
		 * @private
		 */
		public function invokeResumeCallBacks() : void
		{
			if( _resumeCallBacks != null )
			{
				for each( var fn : Function in _resumeCallBacks )
				{
					fn();
				}
				clearResumeCallBack();
			}
		}
		
		/**
		 * @private
		 */
		public function clearResumeCallBack() : void
		{
			_resumeCallBacks = null;
		}
		
		public function cleanUp() : void
		{
			clearResumeCallBack();
			for each( var array : Array in _suspendableListeners )
				for each( var listener : SuspendableListener in  array )
					listener.target.removeEventListener( listener.type, listener.handler );
		}

	}
}


import flash.events.EventDispatcher;
import flash.events.IEventDispatcher;

import mx.binding.Watcher;
import mx.binding.utils.BindingUtils;
import mx.core.mx_internal;
import mx.rpc.Fault;
import mx.rpc.IResponder;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import com.astoolkit.workflow.api.*;

class SuspendableListener
{
	public function SuspendableListener( inTask : IWorkflowTask, inHandler : Function, inThread : uint, inTarget : IEventDispatcher, inType : String ) 
	{
		task = inTask;
		thread = inThread;
		handler = inHandler;
		target = inTarget;
		type = inType;
	}
	
	public var handler : Function;
	public var task : IWorkflowTask;
	public var thread : uint;
	public var target : IEventDispatcher;
	public var type : String;
}

[Event(name="result", type="mx.rpc.events.ResultEvent")]
[Event(name="fault", type="mx.rpc.events.FaultEvent")]
class SuspendableResponder extends EventDispatcher implements IResponder
{
	
	public function SuspendableResponder( inResultFunction : Function, inFaultFunction : Function ) : void
	{
		
	}
	
	public function fault(inFault:Object):void
	{
		dispatchEvent( new FaultEvent(FaultEvent.FAULT, false, false, Fault( inFault ) ) );
	}
	
	public function result(inData:Object):void
	{
		dispatchEvent( new ResultEvent( ResultEvent.RESULT, false, false, inData ) );
		
	}
}